/* 
 * Copyright (C) 2015 fede
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package Boundary;

import Control.CalendarFacade;
import Control.EventFacade;
import Control.WeatherForecastFacade;
import Entity.Event;
import Entity.NotifyLink;
import Entity.NotifyReply;
import Entity.RegisteredUser;
import Entity.WeatherForecast;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.inject.Named;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.view.ViewScoped;
import org.primefaces.event.ScheduleEntryMoveEvent;
import org.primefaces.event.ScheduleEntryResizeEvent;
import org.primefaces.event.SelectEvent;
import org.primefaces.model.DefaultScheduleModel;
import org.primefaces.model.ScheduleModel;

/**
 *
 * @author fede
 */
@Named(value = "calendarGUI")
@ViewScoped
public class CalendarGUI implements Serializable {

    private ScheduleModel eventModel;

    private EventMod event;
    private Event cEvent;
    private String nameSurname;
    private List<NotifyLink> notifyLink;
    private List<NotifyReply> notifyReply;

    @EJB
    private CalendarFacade cf;

    @EJB
    private EventFacade ef;

    @EJB
    private WeatherForecastFacade wff;

    /**
     * PostConstruct
     */
    @PostConstruct
    public void init() {
        notifyLink = new ArrayList<>();
        this.nameSurname = cf.getNameSurname();
        eventModel = new DefaultScheduleModel();
        List<Event> eventX = cf.updateEventNex3DayWeatherAndNotify();
        prepareEvent(eventX, Boolean.TRUE, Boolean.TRUE, Boolean.TRUE);

        eventX = ef.getAllPublicEvent(Boolean.TRUE);
        prepareEvent(eventX, Boolean.FALSE, Boolean.FALSE, Boolean.TRUE);

        eventX = cf.updateWeatherNext1DayAndNotify();
        prepareEvent(eventX, Boolean.TRUE, Boolean.FALSE, Boolean.TRUE);
        notifyReply = cf.getNotifyReply();

        eventX = ef.getAllPublicCalendarEvent(Boolean.TRUE, Boolean.FALSE);
        prepareEvent(eventX, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE);

    }

    private void prepareEvent(List<Event> eventList, Boolean neednotify, Boolean editable, Boolean showDetail) {
        for (int i = 0; i < eventList.size(); i++) {
            if (!eventModel.getEvents().contains(new EventMod(eventList.get(i).getId()))) {

                //TODO: try to simplify this f***ing shit
                if (eventList.get(i).getWeatherForecast() != null) {
                    eventModel.addEvent(new EventMod(
                            eventList.get(i).getLocation(),
                            eventList.get(i).getIndoor_bool(),
                            eventList.get(i).getPublic_bool(),
                            eventList.get(i).getTitle(),
                            eventList.get(i).getStartEventDate(),
                            eventList.get(i).getEndEventDate(),
                            eventList.get(i).getDescription(),
                            eventList.get(i).getAll_day(),
                            eventList.get(i).getWeatherForecast().getDescription(),
                            eventList.get(i).getWeatherForecast().getTemperature(),
                            eventList.get(i).getWeatherForecast().getIcon(), editable,
                            eventList.get(i).getGuest(), showDetail, eventList.get(i).getOwner()));
                    eventModel.getEvents().get(eventModel.getEvents().size() - 1).setId(eventList.get(i).getId());
                } else {
                    eventModel.addEvent(new EventMod(
                            eventList.get(i).getLocation(),
                            eventList.get(i).getIndoor_bool(),
                            eventList.get(i).getPublic_bool(),
                            eventList.get(i).getTitle(),
                            eventList.get(i).getStartEventDate(),
                            eventList.get(i).getEndEventDate(),
                            eventList.get(i).getDescription(),
                            eventList.get(i).getAll_day(), editable,
                            eventList.get(i).getGuest(), showDetail, eventList.get(i).getOwner()));
                    eventModel.getEvents().get(eventModel.getEvents().size() - 1).setId(eventList.get(i).getId());
                }
                if (neednotify) {
                    notifyLink.addAll(cf.getAllNotifyLink(eventList.get(i).getId()));
                }
            }
        }
    }

    /**
     * this method get schedule model
     *
     * @return schedule model
     */
    public ScheduleModel getEventModel() {
        return eventModel;
    }

    /**
     * this method get event
     *
     * @return current Event
     */
    public EventMod getEvent() {
        return event;
    }

    /**
     * this method set event
     *
     * @param event to set
     */
    public void setEvent(EventMod event) {
        this.event = event;
    }

    /**
     * this method get nameSurname string
     *
     * @return string with name and surname of logged user
     */
    public String getNameSurname() {
        return nameSurname;
    }

    /**
     * this method set nameSurname string
     *
     * @param nameSurname string to set
     */
    public void setNameSurname(String nameSurname) {
        this.nameSurname = nameSurname;
    }

    /**
     * This method check if event exist. If exist will update it with new input
     * otherwise create it in the DB. Next update primefaces graphics
     *
     * @param actionEvent from primefaces
     */
    public void addEvent(ActionEvent actionEvent) {

        if (event.getId() == null) {
            eventModel.addEvent(event);
            mapEventOnEvent();
            checkWeatherAndUpdateIfExist();
            ef.save(cEvent, event.getGuest());
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Info", "Event Successfully Created");
            addMessage(message);

        } else {
            eventModel.updateEvent(event);
            mapEventOnEvent();
            WeatherForecast oldForecast = checkWeatherAndUpdateIfExist();
            ef.editE(cEvent, event.getGuest());
            wff.remove(oldForecast);
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Info", "Event Successfully Updated");
            addMessage(message);

        }

        event = new EventMod();

    }

    private WeatherForecast checkWeatherAndUpdateIfExist() {

        WeatherForecast oldForecast = null;
        Long temp = wff.getWeather(event.getLocation(), event.getStartDate());
        if (temp != (long) -1) {
            event.setDescriptionW(wff.getDescription(temp));
            event.setIconW(wff.getIcon(temp));
            event.setTemperatureW(wff.getTemperature(temp));
            event.setHasWeather(Boolean.TRUE);
            Event oldEvent = ef.find(cEvent.getId());
            if (oldEvent != null) {
                oldForecast = ef.find(cEvent.getId()).getWeatherForecast();
            }
            cEvent.setWeatherForecast(wff.getForecast(temp));
        } else {
            event.setHasWeather(Boolean.FALSE);
            event.setDescriptionW(null);
            event.setIconW(null);
            event.setTemperatureW(null);
        }

        return oldForecast;
    }

    /**
     * This method delete event from DB and graphics
     *
     * @param actionEvent from primefaces
     */
    public void deleteEvent(ActionEvent actionEvent) {
        if (event.getId() != null) {
            ef.delete(event.getId());
            eventModel.deleteEvent(event);
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Info", "Event Successfully Deleted");
            addMessage(message);

        }
    }

    private void mapEventOnEvent() {
        cEvent = new Event();
        cEvent.setId(event.getId());
        cEvent.setTitle(event.getTitle());
        cEvent.setDescription(event.getDescription());
        cEvent.setAll_day(event.isAllDay());
        if(cEvent.getAll_day()){
           Calendar cal = Calendar.getInstance();
           cal.setTime(event.getStartDate());
           cal.set(Calendar.HOUR_OF_DAY, 00);
           cal.set(Calendar.MINUTE, 00);
           event.setStartDate(cal.getTime());
           cEvent.setStartEventDate(cal.getTime());
           cal.setTime(event.getStartDate());
           cal.set(Calendar.HOUR_OF_DAY, 23);
           cal.set(Calendar.MINUTE, 59);
           event.setEndDate(cal.getTime());
           cEvent.setEndEventDate(cal.getTime()); 
        }
        else{
        cEvent.setStartEventDate(event.getStartDate());
        cEvent.setEndEventDate(event.getEndDate());
        }
        cEvent.setLocation(event.getLocation());
        cEvent.setPublic_bool(event.getPublic_bool());
        cEvent.setIndoor_bool(event.getIndoor_bool());

    }

    /**
     * This method manage the selection of event in graphic
     *
     * @param selectEvent from primefaces
     */
    public void onEventSelect(SelectEvent selectEvent) {
        event = (EventMod) selectEvent.getObject();
    }

    /**
     * This method manage the selection of date in graphic
     *
     * @param selectEvent from primefaces
     */
    public void onDateSelect(SelectEvent selectEvent) {
        event = new EventMod("", (Date) selectEvent.getObject(), (Date) selectEvent.getObject());
        event.setEndDate(addMinutes(event, 1));
        event.setShowDetail(Boolean.TRUE);
        event.setIndoor_bool(true);
        event.setPublic_bool(false);
    }

    /**
     * This method manage the move of event in graphic
     *
     * @param event ScheduleEntryMoveEvent from primefaces
     */
    public void onEventMove(ScheduleEntryMoveEvent event) {

        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Event moved", "Day delta:" + event.getDayDelta() + ", Minute delta:" + event.getMinuteDelta());
        addMessage(message);

        this.event = (EventMod) eventModel.getEvent(event.getScheduleEvent().getId());
        mapEventOnEvent();
        WeatherForecast oldForecast = checkWeatherAndUpdateIfExist();
        ef.editE(cEvent, this.event.getGuest());
        wff.remove(oldForecast);
    }

    /**
     * This method manage the resize of event in graphic
     *
     * @param event ScheduleEntryMoveEvent from primefaces
     */
    public void onEventResize(ScheduleEntryResizeEvent event) {
        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Event resized", "Day delta:" + event.getDayDelta() + ", Minute delta:" + event.getMinuteDelta());
        addMessage(message);

        this.event = (EventMod) eventModel.getEvent(event.getScheduleEvent().getId());
        mapEventOnEvent();
        WeatherForecast oldForecast = checkWeatherAndUpdateIfExist();
        ef.editE(cEvent, this.event.getGuest());
        wff.remove(oldForecast);
    }

    /**
     * this method get notifylink for graphic
     *
     * @return List Notifylink
     */
    public List<NotifyLink> getNotifyLink() {
        return notifyLink;
    }

    /**
     * this method set notifylink for graphic
     *
     * @param notifyLink to set
     */
    public void setNotifyLink(List<NotifyLink> notifyLink) {
        this.notifyLink = notifyLink;
    }

    /**
     * this method check if notifyLink is empity
     *
     * @return true if empity
     */
    public Boolean isNotifylinkEmpity() {
        return notifyLink.isEmpty();
    }

    /**
     * this method get notifyreply for graphic
     *
     * @return true if empity
     */
    public Boolean isNotifyReplyEmpity() {
        return notifyReply.isEmpty();
    }

    /**
     * this method get notifyreply for graphic
     *
     * @return List Notifyreply
     */
    public List<NotifyReply> getNotifyReply() {
        return notifyReply;
    }

    /**
     * this method set notifyReply for graphic
     *
     * @param notifyReply to set
     */
    public void setNotifyReply(List<NotifyReply> notifyReply) {
        this.notifyReply = notifyReply;
    }

    private void addMessage(FacesMessage message) {
        FacesContext.getCurrentInstance().addMessage(null, message);
    }

    /**
     * This method provide a list of registered user for autocmplete Component
     * of primefaces
     *
     * @param toSearch query to search
     * @return List of registered user to show
     */
    public List<RegisteredUser> getCompleteTxt(String toSearch) {
        List<RegisteredUser> temp = cf.getUserToInvite(toSearch);
        if (event.getGuest() != null) {
            for (RegisteredUser guest : event.getGuest()) {
                temp.remove(guest);
            }
        }

        return temp;
    }

    /**
     * This method manage the graphic Accept/Decline for invite Cancel notify
     * from DB and remove invited user if necessary
     *
     * @param accept reply from user
     * @param notifyReply notify to change
     */
    public void acceptDeclineInvite(Boolean accept, NotifyReply notifyReply) {
        if(ef.find(notifyReply.getEvent().getId())!=null){
        if (!accept) {
            ef.removeInvitedUser(notifyReply);
        } else {
            List<Event> tempEvent = new ArrayList<>();
            tempEvent.add(notifyReply.getEvent());
            if (eventModel.getEvent(notifyReply.getEvent().getId()) != null) {
                eventModel.deleteEvent(eventModel.getEvent(notifyReply.getEvent().getId()));
            }
            prepareEvent(tempEvent, false, false, true);

        }
        cf.deleteNotify(notifyReply);
        }
        this.notifyReply.remove(notifyReply);
    }

    /**
     * This method manage the graphic notification for suggested date in case of
     * bad weather condition Cancel notify from DB and remove invited user if
     * necessary
     *
     * @param notifyLink notify to change
     * @param reply from user
     */
    public void okDeleteNotify(NotifyLink notifyLink, Boolean reply) {
        if(ef.find(notifyLink.getEvent().getId())!=null){
        if (reply == true) {
            Event toMod = notifyLink.getEvent();
            Calendar cal = Calendar.getInstance();
            cal.setTimeInMillis(notifyLink.getSuggestedDate().getTime() + (toMod.getEndEventDate().getTime() - toMod.getStartEventDate().getTime()));
            toMod.setStartEventDate(notifyLink.getSuggestedDate());
            toMod.setEndEventDate(cal.getTime());
            wff.updateWeather(toMod.getWeatherForecast(), toMod.getLocation(), toMod.getStartEventDate());
            event = (EventMod) eventModel.getEvent(toMod.getId());
            event.setStartDate(notifyLink.getSuggestedDate());
            event.setEndDate(cal.getTime());
            event.setDescriptionW(toMod.getWeatherForecast().getDescription());
            event.setIconW(toMod.getWeatherForecast().getIcon());
            event.setTemperatureW(toMod.getWeatherForecast().getTemperature());
            event.setHasWeather(Boolean.TRUE);
            eventModel.updateEvent(event);
            ef.editE(toMod, toMod.getGuest());
        }
        cf.deleteNotifyLink(notifyLink);
        }
        else{
            eventModel.deleteEvent((EventMod) eventModel.getEvent(notifyLink.getEvent().getId()));
        }
        
        this.notifyLink.remove(notifyLink);
    }

    /**
     * This methode manage the selection of the event
     *
     * @param selectEvent event from primefaces
     */
    public void handleSelect(SelectEvent selectEvent) {
        EventMod tempEvent = (EventMod) selectEvent.getObject();
        event.addGuest(tempEvent.getGuest().get(0));

    }

    /**
     * Method to validate the date of event
     */
    public void validateEventDate() {
        if (event.getStartDate().after(event.getEndDate())) {

            event.setEndDate(addMinutes(event, 1));
        }
        if (event.getEndDate().before(event.getStartDate())) {
            event.setEndDate(addMinutes(event, 1));
        }
        
    }

    private Date addMinutes(EventMod event, int minutes) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(event.getStartDate());
        cal.add(Calendar.MINUTE, minutes);
        return cal.getTime();

    }
    
    /**
     * trololo!
     *
     * @return boolean
     */
    public Boolean isJack() {
        return (nameSurname.equals("Jack Nicholson") || nameSurname.equals("jack nicholson"));
    }

    /**
     * constructor
     */
    public CalendarGUI() {

    }

}
